diff --git a/web/modals/threads/gallery/thread-settings-media-gallery-item.react.js b/web/modals/threads/gallery/thread-settings-media-gallery-item.react.js
new file mode 100644
--- /dev/null
+++ b/web/modals/threads/gallery/thread-settings-media-gallery-item.react.js
@@ -0,0 +1,71 @@
+// @flow
+
+import invariant from 'invariant';
+import * as React from 'react';
+
+import { fetchableMediaURI } from 'lib/media/media-utils.js';
+
+import EncryptedMultimedia from '../../../media/encrypted-multimedia.react.js';
+import { usePlaceholder } from '../../../media/media-utils.js';
+
+type MediaSource =
+ | {
+ +kind: 'plain',
+ +uri: string,
+ +thumbHash: ?string,
+ }
+ | {
+ +kind: 'encrypted',
+ +holder: string,
+ +encryptionKey: string,
+ +thumbHash: ?string,
+ };
+type Props = {
+ +imageSource: MediaSource,
+ +imageCSSClass: string,
+ +imageContainerCSSClass: string,
+ +onClick?: () => void,
+};
+function MediaGalleryItem(props: Props) {
+ const { imageSource, imageCSSClass, imageContainerCSSClass } = props;
+
+ const { thumbHash, encryptionKey } = imageSource;
+ const placeholderImage = usePlaceholder(thumbHash, encryptionKey);
+ const imageStyle = React.useMemo(
+ () => ({
+ background: placeholderImage
+ ? `center / cover url(${placeholderImage})`
+ : undefined,
+ }),
+ [placeholderImage],
+ );
+
+ let image;
+ if (imageSource.kind === 'plain') {
+ const uri = fetchableMediaURI(imageSource.uri);
+ image = ;
+ } else if (imageSource.kind === 'encrypted') {
+ const { holder } = imageSource;
+ invariant(encryptionKey, 'encryptionKey undefined for encrypted image');
+ image = (
+